﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Pfz.Threading
{
	/// <summary>
	/// Class similar to Lazy. But instead of only loading values when they are first accessed, it tries to load the
	/// value when the application is idle. Will load immediatelly if the value is requested.
	/// You must instantiate the generic version.
	/// </summary>
	public abstract class BackgroundLoader:
		ThreadSafeDisposable
	{
		private static readonly ManagedAutoResetEvent _event = new ManagedAutoResetEvent();
		internal static readonly HashSet<BackgroundLoader> _items = new HashSet<BackgroundLoader>();

		static BackgroundLoader()
		{
			Thread thread = new Thread(_Run);
			thread.Name = "Pfz.Threading.BackgroundLoader";
			thread.Start();
		}
		private static void _Run()
		{
			var currentThread = Thread.CurrentThread;
			while(true)
			{
				currentThread.IsBackground = true;

				#if !SILVERLIGHT
					currentThread.Priority = ThreadPriority.Lowest;
				#endif

				_event.WaitOne();

				while(true)
				{
					#if !SILVERLIGHT
						while(true)
						{
							// Keeps yielding while there are other threads wanting to run.
							if (!Thread.Yield())
								break;
						}

						currentThread.Priority = ThreadPriority.Normal;
					#endif

					currentThread.IsBackground = false;

					BackgroundLoader item;

					lock(_items)
					{
						item = _items.FirstOrDefault();

						if (item == null)
							break;

						_items.Remove(item);
					}

					try
					{
						lock(item.DisposeLock)
							if (!item.WasDisposed)
								item._CreateValue();
					}
					catch
					{
						// Ignore any exceptions here.
					}

					currentThread.IsBackground = true;

					#if !SILVERLIGHT
						currentThread.Priority = ThreadPriority.Lowest;
					#endif
				}
			}
		}

		internal BackgroundLoader()
		{
			lock(_items)
				_items.Add(this);

			_event.Set();
		}

		/// <summary>
		/// Clears the loaded value or removes this BackgroundLoader from the
		/// pending objects to load.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
				_ClearValueOrRemoveFromLoader();

			base.Dispose(disposing);
		}

		internal abstract void _CreateValue();
		internal abstract void _ClearValueOrRemoveFromLoader();
	}

	/// <summary>
	/// Class similar to Lazy. But instead of only loading values when they are first accessed, it tries to load the
	/// value when the application is idle. Will load immediatelly if the value is requested.
	/// </summary>
	public sealed class BackgroundLoader<T>:
		BackgroundLoader,
		IValueLoader<T>
	where
		T: class
	{
		private Func<T> _createDelegate;

		/// <summary>
		/// Creates a new background loader that will create the value using the default constructor.
		/// </summary>
		public BackgroundLoader()
		{
		}

		/// <summary>
		/// Creates a new BackgroundLoaderByDelegate setting the delegate used
		/// to create the Value.
		/// </summary>
		public BackgroundLoader(Func<T> createDelegate)
		{
			_createDelegate = createDelegate;
		}

		internal override void _CreateValue()
		{
			if (_value != null)
				return;

			if (_createDelegate != null)
			{
				_value = _createDelegate();
				_createDelegate = null;
			}
			else
				_value = (T)Activator.CreateInstance(typeof(T));
		}

		private T _value;
		/// <summary>
		/// Gets the Value, creating it immediatelly if needed.
		/// </summary>
		public T Value
		{
			get
			{
				T result = _value;
				if (result != null)
					return result;

				lock(DisposeLock)
				{
					if (WasDisposed)
						return null;

					result = _value;
					if (result != null)
						return result;

					lock(_items)
						_items.Remove(this);

					_CreateValue();
					return _value;
				}
			}
		}

		/// <summary>
		/// Gets the value that is actually in memory.
		/// If the value was not created or if this object was disposed, returns null.
		/// </summary>
		public T InMemoryValue
		{
			get
			{
				return _value;
			}
		}

		internal override void _ClearValueOrRemoveFromLoader()
		{
			T value = _value;
			if (value == null)
			{
				_createDelegate = null;

				lock(_items)
					_items.Remove(this);

				return;
			}

			_value = null;

			var disposable = value as IDisposable;
			if (disposable != null)
				disposable.Dispose();
		}
	}
}
